package goexpr

import (
	"github.com/google/cel-go/cel"
	"github.com/google/cel-go/common/types/ref"
)

type GoExpr struct {
	Expr      string          // set the expr string
	Variables []cel.EnvOption // set expr variable type
	Vals      any             // set expr variable value
	err       error
}

// Expr 表达式计算
func NewExpr(expr string) *GoExpr {
	return &GoExpr{
		Expr: expr,
	}
}

// WithExprVal set expr with value
func (g *GoExpr) WithExprVal(vals any) *GoExpr {
	g.Vals = vals
	return g
}

func (g *GoExpr) Error() error {
	return g.err
}

// WithVariable set expr string variable
func (g *GoExpr) WithVariable(vars ...cel.EnvOption) *GoExpr {
	g.Variables = vars
	return g
}

func (g *GoExpr) Eval() (ref.Val, *cel.EvalDetails, error) {
	var val ref.Val
	var detail *cel.EvalDetails
	if g.err != nil {
		return nil, detail, g.err
	}
	var env *cel.Env
	env, g.err = newEnvWithOptions(g.Variables...)
	if g.err != nil {
		return nil, detail, g.err
	}
	ast, iss := env.Compile(g.Expr)
	if iss.Err() != nil {
		g.err = iss.Err()
		return nil, detail, g.err
	}
	prg, err := env.Program(ast)
	if err != nil {
		g.err = err
		return nil, detail, g.err
	}

	if g.Vals == nil {
		val, detail, g.err = prg.Eval(make(map[string]interface{}))
	} else {
		val, detail, g.err = prg.Eval(g.Vals)
	}

	return val, detail, g.err
}

// get env with options
func newEnvWithOptions(opts ...cel.EnvOption) (*cel.Env, error) {
	return cel.NewEnv(opts...)
}
